#pragma once

#include "public/build.h"

#ifdef ENABLE_MULTI_THREAD
#include <atomic>
#endif
#include <cassert>
#include <iostream>

namespace renderit
{
	namespace core
	{
		class kobject
		{
		public:
			kobject()
				: ref_count_(1)
				, mem_(0)
			{

			}

			virtual ~kobject()
			{

			}

		public:
			void add_refcount()
			{
				assert(ref_count_ > 0);
#ifdef ENABLE_MULTI_THREAD
				ref_count_.fetch_add(1, std::memory_order_acq_rel);
#else
				ref_count_++;
#endif
			}

			void dec_refcount()
			{
				assert(ref_count_ > 0);
#ifdef ENABLE_MULTI_THREAD
				int ref = ref_count_.fetch_sub(1, std::memory_order_acq_rel);
				if (ref == 1)
				{
					delete this;
				}
#else
				if (--ref_count_ == 0)
				{
					delete this;
				}
#endif
			}

		public:
			virtual void dump() const {}
			virtual void calc_mem() { mem_ = sizeof(*this); }

		private:
#ifdef ENABLE_MULTI_THREAD
			std::atomic<int> ref_count_;
#else
			int ref_count_;
#endif

		protected:
			int mem_;
		};

		template<typename T>
		class ref_ptr
		{
		public:
			ref_ptr()
				: p_(nullptr)
			{
			}

			ref_ptr(T* p)
			{
				p_ = p;
			}

			ref_ptr(const ref_ptr<T>& o)
				: p_(nullptr)
			{
				operator = (o);
			}

			template<typename T2>
			ref_ptr(const ref_ptr<T2>& o)
				: p_(nullptr)
			{
				operator = (o);
			}

			~ref_ptr()
			{
				clear();
			}

		public:
			ref_ptr<T>& operator = (const ref_ptr<T>& o)
			{
				if (p_ != o.p_)
				{
					clear();
					p_ = o.p_;
					if (p_)
					{
						p_->add_refcount();
					}
				}			
				return *this;
			}

			template<typename T2>
			ref_ptr<T>& operator = (const ref_ptr<T2>& o)
			{
				T2 * p = const_cast<T2*>(o.get());
				if (p_ != p)
				{
					clear();
					p_ = dynamic_cast<T*>(p);
					if (p_)
					{
						p_->add_refcount();
					}
				}
				return *this;
			}

			bool operator == (const ref_ptr<T>& o) const
			{
				return p_ == o.p_;
			}

			bool operator == (const T* p) const
			{
				return p_ == p;
			}

		public:
			T* operator ->()
			{
				assert(p_);
				return p_;
			}

			T& operator *()
			{
				assert(p_);
				return *p_;
			}

		public:
			void clear()
			{
				if (p_)
				{
					p_->dec_refcount();
					p_ = nullptr;
				}
			}

			T * get()
			{
				return p_;
			}

			const T * get() const
			{
				return p_;
			}

			void set(T* p)
			{
				clear();
				p_ = p;
			}

			operator bool ()
			{
				return !!p_;
			}

		private:
			T* p_;
		};

		typedef ref_ptr<kobject> kobject_ptr;
	}
}